﻿/**
 * author:  Martin Hrabos
 * year:    2016
 * email:   hrabosmartin@gmail.com
 * 
 * FIT Brno University of Technology / ASCR Brno
 */

using HDF5DotNet;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace CorrCL
{
    /*
     * Class related to writing/reading results
     */
    class SignalFileWriter
    {
        string path;

        // Create object with file path
        public SignalFileWriter(string path)
        {
            this.path = path;
        }

        // Function for reading HDF5 files
        public ResultMatrix ReadH5()
        {
            try
            {
                const int RANK = 1;

                long[] dims = new long[RANK];


                H5FileId fileId = H5F.open(path, H5F.OpenMode.ACC_RDONLY);

                H5DataSetId dataSetId = H5D.open(fileId, "Labels");
                H5DataSpaceId dataSpace = H5D.getSpace(dataSetId);
                long[] size = H5S.getSimpleExtentDims(dataSpace);
                H5DataTypeId dataType = H5D.getType(dataSetId);

                byte[] labelsArray = new byte[size[0]];

                H5D.read(dataSetId, new H5DataTypeId(H5T.H5Type.NATIVE_B8), new H5Array<byte>(labelsArray));

                List<string> labels = byteArrayToLabels(labelsArray);

                // Read Frequency
                dataSetId = H5D.open(fileId, "Frequency");
                dataSpace = H5D.getSpace(dataSetId);
                size = H5S.getSimpleExtentDims(dataSpace);
                dataType = H5D.getType(dataSetId);
                float[] freqArray = new float[size[0]];
                H5D.read(dataSetId, new H5DataTypeId(H5T.H5Type.NATIVE_FLOAT), new H5Array<float>(freqArray));

                // Read WindowSize
                dataSetId = H5D.open(fileId, "WindowSize");
                dataSpace = H5D.getSpace(dataSetId);
                size = H5S.getSimpleExtentDims(dataSpace);
                dataType = H5D.getType(dataSetId);
                long[] wsArray = new long[size[0]];
                H5D.read(dataSetId, new H5DataTypeId(H5T.H5Type.NATIVE_LONG), new H5Array<long>(wsArray));

                // Read WindowOffset
                dataSetId = H5D.open(fileId, "WindowOffset");
                dataSpace = H5D.getSpace(dataSetId);
                size = H5S.getSimpleExtentDims(dataSpace);
                dataType = H5D.getType(dataSetId);
                long[] woArray = new long[size[0]];
                H5D.read(dataSetId, new H5DataTypeId(H5T.H5Type.NATIVE_LONG), new H5Array<long>(woArray));

                // Read WindowOffset
                dataSetId = H5D.open(fileId, "OriginSize");
                dataSpace = H5D.getSpace(dataSetId);
                size = H5S.getSimpleExtentDims(dataSpace);
                dataType = H5D.getType(dataSetId);
                long[] osArray = new long[size[0]];
                H5D.read(dataSetId, new H5DataTypeId(H5T.H5Type.NATIVE_LONG), new H5Array<long>(osArray));

                // Read WindowOffset
                dataSetId = H5D.open(fileId, "SourceFile");
                dataSpace = H5D.getSpace(dataSetId);
                size = H5S.getSimpleExtentDims(dataSpace);
                dataType = H5D.getType(dataSetId);
                byte[] byteArray = new byte[size[0]];
                H5D.read(dataSetId, new H5DataTypeId(H5T.H5Type.NATIVE_B8), new H5Array<byte>(byteArray));

                ResultMatrix res = new ResultMatrix(labels, freqArray[0]);
                res.windowOffset = woArray[0];
                res.windowSize = wsArray[0];
                res.sourceFile = Encoding.ASCII.GetString(byteArray);

                int signalCount = (labels.Count * labels.Count - labels.Count) / 2;
                

                // Read channels
                int cnt = 0;
                for (int i = 0; i < labels.Count - 1; i++)
                {
                    for (int j = i + 1; j < labels.Count; j++)
                    {
                        dataSetId = H5D.open(fileId, "Data" + cnt);
                        dataSpace = H5D.getSpace(dataSetId);
                        size = H5S.getSimpleExtentDims(dataSpace);
                        dataType = H5D.getType(dataSetId);
                        dataType = H5T.copy(H5T.H5Type.NATIVE_FLOAT);

                        float[,] data = new float[size[0],size[1]];
                        H5D.read(dataSetId, new H5DataTypeId(H5T.H5Type.NATIVE_FLOAT), new H5Array<float>(data));

                        List<float[]> blocks = new List<float[]>();

                        for (int k=0; k<data.GetLength(0); k++)
                        {
                            float[] tmp = new float[data.GetLength(1)];
                            for (int l=0; l<data.GetLength(1); l++)
                            {
                                tmp[l] = data[k, l];
                            }
                            blocks.Add((float[])tmp.Clone());
                            
                        }

                        
                        res.Add(new List<float[]>(blocks), j - 1, i);
                        res.results.ElementAt(cnt).originSize = osArray[0];
                        cnt++;
                    }
                }
                return res;
            }

            catch (Exception e)
            {
                MessageBox.Show("Reading from file failed. Probably unsupported file format.", "Read Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return null;
            }
        }


        public void WriteCSV(ResultPoint resultPoint, float frequency)
        {
            StreamWriter file = new StreamWriter(path);
            List<string> labels = new List<string>();


            for (long i=0; i<resultPoint.getBlocks().Count; i++)
            {
                labels.Add("Block" + i);
            }

            string line = "'Elapsed time'";
            foreach (string label in labels)
            {
                line += string.Format(",'{0}'", label);
            }
            file.WriteLine(line);

            line = "'hh:mm:ss.mmm'";
            foreach (string label in labels)
            {
                line += ",'uv'";
            }
            file.WriteLine(line);
            CultureInfo en_US = new CultureInfo("en-US");
            //int length = res.results[0].getSignal().Length;
            int length = resultPoint.getBlocks().First().Length;
            for (int i = 0; i < length-1; i++)
            {
                TimeSpan t = TimeSpan.FromSeconds(i / frequency);
                string str = string.Format("'{0:D2}:{1:D2}:{2:D2}.{3:D3}'",
                            t.Hours,
                            t.Minutes,
                            t.Seconds,
                            t.Milliseconds);
                for (int j = 0; j < resultPoint.getBlocks().Count; j++)
                {
                    str += string.Format(new CultureInfo("en-US"), ",{0}", resultPoint.getBlocks()[j][i]);
                }
                file.WriteLine(str);
            }


            file.Close();
        }

        // Function for conversion labelList to byte array
        private byte[] labelsToByteArray(List<string> labels)
        {
            List<byte> byteArray = new List<byte>();
            foreach (string label in labels)
            {
                byte[] byteString = (Encoding.ASCII.GetBytes(label));
                foreach (byte b in byteString)
                {
                    byteArray.Add(b);
                }
                byteArray.Add(0);
            }
            return byteArray.ToArray();
        }

        // Function for conversion byte array to labelList
        private List<string> byteArrayToLabels(byte[] byteArray)
        {
            List<string> labels = new List<string>();

            List<Byte> tmpList = new List<byte>();

            foreach (Byte b in byteArray)
            {
                if (b == 0)
                {
                    labels.Add(Encoding.ASCII.GetString(tmpList.ToArray()));
                    tmpList.Clear();
                } else
                {
                    tmpList.Add(b);
                }
            }
            return labels;
        }

        // Function for writing to CSV file
        public void WriteCSV(ResultMatrix res, float freq)
        {
            StreamWriter file = new StreamWriter(path);
            List<string> labels = new List<string>();

            for (int i = 0; i < res.labels.Count - 1; i++)
            {
                for (int j = i + 1; j < res.labels.Count; j++)
                {
                    labels.Add(res.labels[i] + "_x_"+ res.labels[j]);
                }
            }

            string line = "'Block number'";
            foreach (string label in labels)
            {
                line += string.Format(",'{0}'", label);
            }
            file.WriteLine(line);

            line = "'hh:mm:ss.mmm'";
            foreach (string label in labels)
            {
                line += ",'uv'";
            }
            file.WriteLine(line);
            CultureInfo en_US = new CultureInfo("en-US");
            //int length = res.results[0].getSignal().Length;
            int length = res.results.First().getSignal().Length;
            for (int i=0; i<length; i++)
            {
                //TimeSpan t = TimeSpan.FromSeconds((i - length / 2) / freq);
                TimeSpan t = TimeSpan.FromSeconds(i / freq);
                string str = string.Format("'{0:D2}:{1:D2}:{2:D2}.{3:D3}'",
                            t.Hours,
                            t.Minutes,
                            t.Seconds,
                            t.Milliseconds);
                for (int j=0; j< res.results.Count; j++)
                {
                    //str += string.Format(new CultureInfo("en-US"), ",{0}", res.results[j].getSignal()[i]);
                    str += string.Format(new CultureInfo("en-US"), ",{0}", res.results.ElementAt(j).getSignal()[i]);
                }
                file.WriteLine(str);
            }


            file.Close();
        }

        public void WriteH5(ResultMatrix res, float frequency, long windowSize, long windowOffset)
        {
            try
            {

                List<string> labels = res.labels;
                BlockingCollection<ResultPoint> results = res.results;

                H5FileId fileId = H5F.create(path, H5F.CreateMode.ACC_TRUNC);

                H5DataTypeId typeId;


                // Write labels
                long[] dims = new long[1];
                byte[] labelsArray = labelsToByteArray(labels);
                dims[0] = labelsArray.Length;
                typeId = H5T.copy(H5T.H5Type.NATIVE_B8);
                H5DataSpaceId spaceId = H5S.create_simple(1, dims);
                H5DataSetId dataSetId = H5D.create(fileId, "Labels", typeId, spaceId);
                H5D.write(dataSetId, new H5DataTypeId(H5T.H5Type.NATIVE_B8), new H5Array<byte>(labelsArray));
                H5D.close(dataSetId);
                H5S.close(spaceId);


                long blockIndex = 0;
                foreach (ResultPoint rp in res.results)
                {
                    List<float[]> blocks = rp.getBlocks();

                    dims = new long[2];
                    

                    dims[0] = blocks.Count;
                    dims[1] = blocks[0].Length;
                    float[,] myData = new float[blocks.Count, blocks[0].Length];

                    for (int i = 0; i < blocks.Count; i++)
                    {
                        for (int j = 0; j < blocks[i].Length; j++)
                        {
                            myData[i, j] = blocks[i][j];
                        }
                    }

                    spaceId = H5S.create_simple(2, dims);
                    typeId = H5T.copy(H5T.H5Type.NATIVE_FLOAT);
                    dataSetId = H5D.create(fileId, "Data"+blockIndex, typeId, spaceId);
                    H5D.write(dataSetId, typeId, new H5Array<float>(myData));
                    H5D.close(dataSetId);
                    H5S.close(spaceId);

                    blockIndex++;
                }



                // Write frequency
                dims[0] = 1;
                spaceId = H5S.create_simple(1, dims);
                typeId = H5T.copy(H5T.H5Type.NATIVE_FLOAT);
                dataSetId = H5D.create(fileId, "Frequency", typeId, spaceId);
                H5D.write(dataSetId, new H5DataTypeId(H5T.H5Type.NATIVE_FLOAT), new H5Array<float>(new float[] { frequency }));
                H5D.close(dataSetId);
                H5S.close(spaceId);

                // Write windowSize
                dims[0] = 1;
                spaceId = H5S.create_simple(1, dims);
                typeId = H5T.copy(H5T.H5Type.NATIVE_LONG);
                dataSetId = H5D.create(fileId, "WindowSize", typeId, spaceId);
                H5D.write(dataSetId, new H5DataTypeId(H5T.H5Type.NATIVE_LONG), new H5Array<long>(new long[] { windowSize }));
                H5D.close(dataSetId);
                H5S.close(spaceId);

                // Write windowOffset
                dims[0] = 1;
                spaceId = H5S.create_simple(1, dims);
                typeId = H5T.copy(H5T.H5Type.NATIVE_LONG);
                dataSetId = H5D.create(fileId, "WindowOffset", typeId, spaceId);
                H5D.write(dataSetId, new H5DataTypeId(H5T.H5Type.NATIVE_LONG), new H5Array<long>(new long[] { windowOffset }));
                H5D.close(dataSetId);
                H5S.close(spaceId);

                // Write Origin Size
                dims[0] = 1;
                spaceId = H5S.create_simple(1, dims);
                typeId = H5T.copy(H5T.H5Type.NATIVE_LONG);
                dataSetId = H5D.create(fileId, "OriginSize", typeId, spaceId);
                H5D.write(dataSetId, new H5DataTypeId(H5T.H5Type.NATIVE_LONG), new H5Array<long>(new long[] { res.results.ElementAt(0).originSize }));
                H5D.close(dataSetId);
                H5S.close(spaceId);

                // Write Origin Size
                dims[0] = res.sourceFile.Length;
                spaceId = H5S.create_simple(1, dims);
                typeId = H5T.copy(H5T.H5Type.NATIVE_B8);
                dataSetId = H5D.create(fileId, "SourceFile", typeId, spaceId);
                byte[] byteArr = Encoding.ASCII.GetBytes(res.sourceFile);
                H5D.write(dataSetId, new H5DataTypeId(H5T.H5Type.NATIVE_B8), new H5Array<byte>(byteArr));
                H5D.close(dataSetId);
                H5S.close(spaceId);

                //H5D.close(dataSetNamesId);
                //H5S.close(spaceStringsId);


                H5F.close(fileId);
            }
            catch (Exception e)
            {
                MessageBox.Show("Writing to file failed", "Write Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }

        }
    }
}
